package com.wchat.server;

import cn.hutool.core.collection.CollectionUtil;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.wchat.bean.entity.*;
import com.wchat.bean.model.MsgPayload;
import com.wchat.bean.model.Session;
import com.wchat.bean.model.body.AckBody;
import com.wchat.bean.model.body.ChatMsgBody;
import com.wchat.bean.model.body.EventBody;
import com.wchat.bean.model.enums.ChatTypeEm;
import com.wchat.bean.model.enums.HeaderTypeEm;
import com.wchat.bean.model.enums.TypeEm;
import com.wchat.mapper.*;
import com.wchat.server.attribute.Attributes;
import com.wchat.utils.JwtUtil;
import com.wchat.utils.SnowFlakeUtil;
import io.netty.channel.Channel;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @author : willian fu
 * @date : 2021/8/30
 */
@Slf4j
@Component
@EnableAsync
public class ClientManager {

    @Getter
    private final static Map<Integer, Channel> clients = new ConcurrentHashMap<>();

    @Getter
    private final static Map<Integer, Set<Integer>> groups = new ConcurrentHashMap<>();

    private static final String MSG_CATCH_KEY = "msg:buffer:";

    @Autowired
    private UsersMapper usersMapper;

    @Autowired
    private JwtUtil jwtUtil;

    @Autowired
    private UserGroupsMapper userGroupsMapper;

    @Autowired
    private FriendsMapper friendsMapper;

    @Autowired
    private ChatMsgsMapper chatMsgsMapper;

    @Autowired
    private StringRedisTemplate redisTemplate;

    @PostConstruct
    public void loadGroups() {
        userGroupsMapper.selectList(new QueryWrapper<UserGroups>().select("user_id", "group_id")).forEach(ug -> {
            Set<Integer> userSet = groups.get(ug.getGroupId());
            if (null == userSet) {
                userSet = new HashSet<>();
                groups.put(ug.getGroupId(), userSet);
            }
            userSet.add(ug.getUserId());
        });
    }

    public void kickOut(Integer id, MsgPayload payload) {
        Channel channel = clients.get(id);
        if (null != channel) {
            sendMsg(id, payload);
            channel.close();
            log.warn("用户[{}]被踢下线", id);
        }
    }

    public void kickOut(Channel channel, MsgPayload payload) {
        Session session = getSession(channel);
        if (null != session) {
            log.warn("用户[{}]被踢下线", session.getUserId());
        }
        channel.writeAndFlush(new TextWebSocketFrame(JSONObject.toJSONString(payload)));
        channel.close();
    }

    public void online(Integer userId, Channel channel) {
        clients.put(userId, channel);
        //loadingHistoryMsg(userId, channel);
        log.info("用户[{}]上线", userId);
    }

    public List<MsgPayload> getHistoryMsg(Integer userId){
        List<MsgPayload> list = new ArrayList<>();
        Map<Object, Object> entries = redisTemplate.boundHashOps(MSG_CATCH_KEY + userId).entries();
        if (CollectionUtil.isNotEmpty(entries)){
            entries.values().forEach(v -> list.add(JSONObject.parseObject(v.toString(), MsgPayload.class)));
        }
        return list;
    }

    public void offline(Channel channel) {
        Session session = getSession(channel);
        if (null != session) {
            clients.remove(session.getUserId());
            log.warn("用户[{}]主动下线", session.getUserId());
        }
    }

    public Session getSession(Integer id) {
        return getSession(clients.get(id));
    }

    public Session getSession(Channel channel) {
        if (null != channel) {
            return channel.attr(Attributes.SESSION).get();
        }
        return null;
    }

    private void catchSingleMsg(Integer targetUser, MsgPayload payload) {
        if (payload.getBody() instanceof ChatMsgBody) {
            Date time = GregorianCalendar.getInstance().getTime();
            ChatMsgBody body = (ChatMsgBody) payload.getBody();
            chatMsgsMapper.insert(ChatMsgs.builder()
                    .msgId(payload.getMsgId())
                    .chatType(payload.isGroup() ? ChatTypeEm.GROUP : ChatTypeEm.SINGLE)
                    .sender(body.getUserId())
                    .target(targetUser)
                    .msgType(body.getMessage().getType())
                    .content(body.getMessage().getContent())
                    .readed(false).recall(false).isSend(false)
                    .created(time).updated(time).build());
        }
        redisTemplate.opsForHash()
                .put(MSG_CATCH_KEY + targetUser, payload.getMsgId().toString(),
                        JSONObject.toJSONString(payload));
    }

    public void cleanMsgCatch(Integer targetUser, Long msgId) {
        chatMsgsMapper.updateById(ChatMsgs.builder().msgId(msgId).isSend(true).build());
        redisTemplate.opsForHash().delete(MSG_CATCH_KEY + targetUser, msgId);
    }

    public void sendEvent(Integer userId, TypeEm eventType, EventBody eventBody){
        sendMsg(userId, MsgPayload.builder()
                .header(HeaderTypeEm.EVENT)
                 .type(eventType)
                .body(eventBody)
                .build());
    }

    public void sendMsg(Integer userId, MsgPayload payload) {
        Channel channel = clients.get(userId);
        if (null != channel) {
            channel.writeAndFlush(new TextWebSocketFrame(JSONObject.toJSONString(payload)));
        }
    }

    /**
     * 判断发送权限
     *
     * @param from     谁
     * @param to       发送给谁
     * @param isFriend 时单聊还是群聊
     * @return 是否允许发送
     */
    public boolean getSendPrem(Integer from, Session fromSession, Integer to, boolean isFriend) {
        try {
            if (isFriend) {
                return fromSession.getFriends().contains(to);
            } else {
                fromSession.getGroups().contains(to);
            }
        } catch (Exception e) {
        }
        return false;
    }

    @Async
    public void sendChatMsgQos2(Integer id, MsgPayload payload) {
        //把消息插入数据库
        catchSingleMsg(id, payload);
        sendMsg(id, payload);
    }

    @Async
    public void sendChatMsgToGroupQos2(Integer sender, Integer groupId, MsgPayload payload) {
        groups.getOrDefault(groupId, Collections.emptySet()).forEach(v -> {
            if (!v.equals(sender)) {
                catchSingleMsg(v, payload);
                sendMsg(v, payload);
            }
        });
    }

    public void reloadSession(Integer userId, Channel channel) {
        Channel ch = channel;
        Session build = Session.builder().userId(userId)
                .friends(new HashSet<>(friendsMapper.getUserFriends(userId)))
                .groups(userGroupsMapper.getUserGroups(userId))
                .build();
        if (null == ch) {
            ch = clients.get(userId);
        }
        if (null != channel) {
            ch.attr(Attributes.SESSION).set(build);
        }
    }

    public void authentication(Channel channel, String token) {
        Users users = jwtUtil.jwtGetUserByToken(token);
        if (null == users) {
            channel.close();
            log.warn("客户端[token = {}]鉴权失败", token);
        }else {
            reloadSession(users.getUserId(), channel);
            online(users.getUserId(), channel);
        }
    }

}
